﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using System.Diagnostics;
using ICSharpCode.SharpZipLib.GZip;
using ICSharpCode.SharpZipLib.Zip.Compression;

namespace WinCryptor2008
{
    public sealed class EncryptionRoutines
    {
        private byte[] key;
        private byte[] iv;
        private bool initialized = false;
        private RijndaelManaged rijndael = null;
        private string headerString = "WinCryptor2008";
        private byte[] headerBytes;
        private bool canceled;

        public event EventHandler<ProgressEventArgs> Progress;
        public event EventHandler<FinishedEventArgs> Finished;

        public string GenerateHash(string source)
        {
            return Convert.ToBase64String(new SHA384Managed().ComputeHash(new UnicodeEncoding().GetBytes(source)));
        }

        public void Initialize(string password)
        {
            rijndael = new RijndaelManaged();

            PasswordDeriveBytes pdb = new PasswordDeriveBytes(password, new MD5CryptoServiceProvider().ComputeHash(new UnicodeEncoding().GetBytes(password)));
            key = pdb.GetBytes(32);
            iv = pdb.GetBytes(16);

            headerBytes = new UnicodeEncoding().GetBytes(headerString);

            rijndael.Key = key;
            rijndael.IV = iv;
            rijndael.BlockSize = 128;
            rijndael.Padding = PaddingMode.PKCS7;

            initialized = true;
        }

        public void CancelTransform()
        {
            if (!initialized) return;
            canceled = true;
        }

        public bool TransformFile(string inFile, string outFile, bool encrypt)
        {
            if (!initialized)
            {
                OnFinished(ReturnType.Badly);
                return false;
            }
            if (!File.Exists(inFile))
            {
                OnFinished(ReturnType.Badly);
                return false;
            }

            FileStream inStream = null, outStream = null;
            CryptoStream encStream = null;
            GZipOutputStream gzipOutputStream = null;
            GZipInputStream gzipInputStream = null;
            ReturnType retVal = ReturnType.Badly;

            try
            {
                inStream = new FileStream(inFile, FileMode.Open, FileAccess.Read);
                outStream = new FileStream(outFile, FileMode.Create, FileAccess.Write);

                byte[] buffer = new byte[4096];
                long bytesRead = 0;
                long fileSize = inStream.Length;
                int bytesToWrite = 0;
                bool success = false;

                if (encrypt)
                {
                    encStream = new CryptoStream(outStream, rijndael.CreateEncryptor(key, iv), CryptoStreamMode.Write);
                    gzipOutputStream = new GZipOutputStream(encStream);
                    gzipOutputStream.SetLevel(Deflater.BEST_COMPRESSION);
                    gzipOutputStream.IsStreamOwner = false;
                    gzipOutputStream.Write(headerBytes, 0, headerBytes.Length);
                    while (true)
                    {
                        if (canceled)
                        {
                            retVal = ReturnType.Canceled;
                            break;
                        }
                        bytesToWrite = inStream.Read(buffer, 0, 4096);
                        if (bytesToWrite == 0)
                        {
                            success = true;
                            break;
                        }
                        gzipOutputStream.Write(buffer, 0, bytesToWrite);
                        bytesRead += bytesToWrite;
                        double pourcent = (double)((double)bytesRead / (double)fileSize) * 100.0;
                        OnProgress((int)Math.Round(pourcent));
                    }
                    if (success)
                    {
                        OnProgress(100);
                        retVal = ReturnType.Well;
                    }
                }
                else
                {
                    encStream = new CryptoStream(inStream, rijndael.CreateDecryptor(key, iv), CryptoStreamMode.Read);
                    gzipInputStream = new GZipInputStream(encStream);
                    gzipInputStream.IsStreamOwner = false;
                    byte[] test = new byte[headerBytes.Length];
                    gzipInputStream.Read(test, 0, test.Length);

                    string testStr = new UnicodeEncoding().GetString(test);
                    if (testStr != headerString)
                    {
                        retVal = ReturnType.IncorrectPassword;
                    }
                    else
                    {
                        while (true)
                        {
                            if (canceled)
                            {
                                retVal = ReturnType.Canceled;
                                break;
                            }
                            bytesToWrite = gzipInputStream.Read(buffer, 0, 4096);
                            if (bytesToWrite == 0)
                            {
                                success = true;
                                break;
                            }
                            outStream.Write(buffer, 0, bytesToWrite);
                            bytesRead += bytesToWrite;
                            double pourcent = (double)((double)bytesRead / (double)fileSize) * 100.0;
                            OnProgress((int)Math.Round(pourcent));
                        }
                        if (success)
                        {
                            OnProgress(100);
                            retVal = ReturnType.Well;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine("*****************ERROR*****************");
                Trace.WriteLine(ex.ToString());
                Trace.WriteLine("****************ERROR*****************");
            }
            finally
            {
                if (gzipOutputStream != null) gzipOutputStream.Close();
                if (gzipInputStream != null) gzipInputStream.Close();
                if (encStream != null) 
                    try { encStream.Close(); } 
                    catch (CryptographicException) { }
                if (inStream != null) inStream.Close();
                if (outStream != null) outStream.Close();
            }
            if (retVal == ReturnType.IncorrectPassword)
            {
                File.Delete(outFile);
            }
            OnFinished(retVal);
            canceled = false;
            return true;
        }

        private void OnProgress(int progress)
        {
            if (Progress != null) Progress(this, new ProgressEventArgs(progress));
        }

        private void OnFinished(ReturnType returnType)
        {
            if (Finished != null) Finished(this, new FinishedEventArgs(returnType));
        }
    }
}
